/**
 * Copyright (c) 2017 hadlinks, All Rights Reserved.
 */
package com.bugull.farm.core.control.example;

import com.alibaba.fastjson.JSONObject;
import com.bugull.farm.core.control.AbstractBuguEventManager;
import com.bugull.farm.core.enums.BuguEventEnum;
import com.bugull.farm.core.enums.CommandEnum;
import com.bugull.farm.core.frame.exception.BuguServiceException;
import com.bugull.farm.core.server.FarmServer;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;

import java.util.concurrent.*;

/**
 * Project Name: bugu-farm-household
 * Package Name: com.bugull.farm.core.example
 * ClassName: CmdResEventManager
 * Function: 事件源管理器，提供对事件的各种监听的接口.
 * date: 2017/10/27 18:08
 * @author songwei (songw@hadlinks.com)
 * @since JDK 1.8 
 */
public class CmdResEventManager extends AbstractBuguEventManager<CmdResEventListener> {

    private AttributeKey<ConcurrentMap<String, Runnable>> cmdAttributeKey = AttributeKey.valueOf("cmd");
    private Attribute<ConcurrentMap<String, Runnable>> attr = ctx.channel().attr(cmdAttributeKey);

    public CmdResEventManager(ChannelHandlerContext ctx) {
        super(ctx);
        attr.set(new ConcurrentHashMap<>());
    }

    /**
     * 增加事件监听器，该监听器可以设置在timeout时间后，自动触发执行task任务
     * @param listener
     * @param timeout
     * @param timeUnit
     * @param cmd
     * @param serial
     * @param terminalSN
     */
    public void addListener(CmdResEventListener listener, long timeout, TimeUnit timeUnit, CommandEnum cmd, String serial, String terminalSN) {
        synchronized (this) {
            if (listener != null && !listeners.contains(listener)) {
                this.listeners.add(listener);
            }
        }
        Runnable task = () -> {
            CmdResEvent event = new CmdResEvent(this);
            event.setCmd(cmd);
            event.setSerial(serial);
            event.setTerminalSN(terminalSN);
            event.setType(BuguEventEnum.EVENT_TYPE_CMD_TIMEOUT);
            notifyTimeout(event);
        };
        Future f = FarmServer.getScheduledExecutors().schedule(task, timeout, timeUnit);
        f.cancel(Boolean.TRUE);
        addCmd(cmd.getHexCmd() + serial + terminalSN, task);
    }

    /**
     * 增加命令监听记录的缓存
     * @param uId
     * @param task
     */
    public void addCmd(String uId, Runnable task) {
        ConcurrentMap<String, Runnable> cmdMap = attr.get();
        cmdMap.put(uId, task);
    }

    /**
     * 根据命令监听记录中的唯一标识，查找计划任务，如超时计划任务
     * @param uId
     */
    public Runnable getCmd(String uId) {
        ConcurrentMap<String, Runnable> cmdMap = attr.get();
        return cmdMap.get(uId);
    }

    /**
     * 根据命令监听记录唯一标识uId和对应的计划任务对象task进行删除对应的命令监听记录
     * @param uId
     * @param task
     */
    public void removeCmd(String uId, Runnable task) {
        ConcurrentMap<String, Runnable> cmdMap = attr.get();
        cmdMap.remove(uId, task);
    }

    /**
     * 触发命令超时事件
     * @param event
     */
    public void notifyTimeout(CmdResEvent event) {
        for (CmdResEventListener listener : listeners) {
            listener.fireEvent(event);
            //触发超时推送，意味着事件监听到此为止，移除监听记录
            String uId = event.getCmd().getHexCmd() + event.getSerial() + event.getTerminalSN();
            removeCmd(uId, getCmd(uId));
        }
    }

    /**
     * 触发命令超时事件
     * @param event
     */
    public void notifyRes(CmdResEvent event) {
        for (CmdResEventListener listener : listeners) {
            listener.fireEvent(event);
            //响应事件触发，意味着事件监听到此为止，移除监听记录，并且移除相关的计划任务
            String uId = event.getCmd().getHexCmd() + event.getSerial() + event.getTerminalSN();
            FarmServer.getScheduledExecutors().remove(getCmd(uId));
            removeCmd(uId, getCmd(uId));
        }
    }

    /**
     * 事件推送的外部调用接口，直接从通道上下文ctx和消息体frame中获取信息并推送给命令响应事件监听器
     * @param ctx
     * @param frame
     * @throws Exception
     */
    public static void notifyResWithCxt(ChannelHandlerContext ctx, JSONObject frame) throws Exception{
        AttributeKey<AbstractBuguEventManager> managerAttributeKey = AttributeKey.valueOf("manager");
        Attribute<AbstractBuguEventManager> attr = ctx.channel().attr(managerAttributeKey);
        if (attr.get() == null) {
            throw new BuguServiceException("通道未注册监听器事件源!!!");
        }
        CmdResEventManager cmdResEventManager = (CmdResEventManager)attr.get();
        CmdResEvent event = new CmdResEvent(cmdResEventManager);
        event.setCmd(CommandEnum.getCommandEnumByName(frame.getString("cmd")));
        event.setTerminalSN(frame.getString("terminalSN"));
        event.setSerial(frame.getLong("date").toString());
        for (CmdResEventListener listener : cmdResEventManager.listeners) {
            listener.fireEvent(event);
            //响应事件触发，意味着事件监听到此为止，移除监听记录，并且移除相关的计划任务
            String uId = event.getCmd().getHexCmd() + event.getSerial() + event.getTerminalSN();
            FarmServer.getScheduledExecutors().remove(cmdResEventManager.getCmd(uId));
            cmdResEventManager.removeCmd(uId, cmdResEventManager.getCmd(uId));
        }
    }
}
